package it.muia.ricchefacce.utils;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import org.richfaces.json.JSONArray;
import org.richfaces.json.JSONException;
import org.richfaces.json.JSONMap;
import org.richfaces.json.JSONObject;

import com.google.common.collect.Lists;

public class ImageGenerator
{
	private final static String DATA_VERSION_KEY = "dataVersion";

	private final static String IMAGE_FORMAT_KEY = "imageFormat";

	private final static String CANVAS_WIDTH_KEY = "canvasWidth";

	private final static String CANVAS_HEIGHT_KEY = "canvasHeight";

	private final static String PEN_COLOR_KEY = "penColor";

	private final static String PEN_WIDTH_KEY = "penWidth";

	private final static String CLICK_STREAM_KEY = "clickstream";

	private final static String EVENT_ACTION_KEY = "action";

	private final static String EVENT_ACTION_START = "gestureStart";

	private final static String EVENT_ACTION_CONTINUE = "gestureContinue";

	private final static String EVENT_ACTION_RESUME = "gestureResume";

	private final static String EVENT_ACTION_SUSPEND = "gestureSuspend";

	public static byte[] generateImage(String jsonString)
	{
		try
		{
			JSONMap jsonMap = new JSONMap(jsonString);
			int dataVersion = new Integer(jsonMap.get(DATA_VERSION_KEY).toString());
			if (dataVersion != 1)
			{
				throw new RuntimeException("Unsupported data version");
			}
			int canvasWidth = new Integer(jsonMap.get(CANVAS_WIDTH_KEY).toString());
			if (canvasWidth < 0)
			{
				throw new RuntimeException("Invalid canvasWidth");
			}
			int canvasHeight = new Integer(jsonMap.get(CANVAS_HEIGHT_KEY).toString());
			if (canvasHeight < 0)
			{
				throw new RuntimeException("Invalid canvasHeight");
			}
			int sizeX = canvasWidth;
			int sizeY = canvasHeight;
			int scalingFactor = Math.min(sizeX / canvasWidth, sizeY / canvasHeight);
			String penColor = jsonMap.get(PEN_COLOR_KEY).toString();
			int penWidth = Math.max(new Integer(jsonMap.get(PEN_WIDTH_KEY).toString()) * scalingFactor, 1);

			List<List<Point>> polylines = Lists.newArrayList();
			int polyline = -1;
			JSONArray clickStreamArray = new JSONArray(jsonMap.get(CLICK_STREAM_KEY).toString());
			for (int i = 0; i < clickStreamArray.length(); i++)
			{
				JSONObject event = clickStreamArray.getJSONObject(i);
				int x = event.getInt("x") * scalingFactor;
				int y = event.getInt("y") * scalingFactor;
				if (event.getString(EVENT_ACTION_KEY).equals(EVENT_ACTION_START) || event.getString(EVENT_ACTION_KEY).equals(EVENT_ACTION_RESUME))
				{
					polyline += 1;
					List<Point> points = new ArrayList<Point>();
					points.add(new Point(x, y));
					polylines.add(polyline, points);
				}
				else if (event.getString(EVENT_ACTION_KEY).equals(EVENT_ACTION_CONTINUE))
				{
					if (polyline >= 0)
					{
						polylines.get(polyline).add(new Point(x, y));
					}
				}
				else if (event.getString(EVENT_ACTION_KEY).equals(EVENT_ACTION_SUSPEND))
				{
					if (polyline >= 0)
					{
						polylines.get(polyline).add(new Point(x, y));
					}
				}
			}
			BufferedImage image = drawUpsampledPolyline(sizeX, sizeY, penColor, penWidth, polylines);
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			ImageIO.write(image, jsonMap.get(IMAGE_FORMAT_KEY).toString(), out);
			return out.toByteArray();
		}
		catch (JSONException e)
		{
			throw new RuntimeException(e);
		}
		catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	private static BufferedImage drawUpsampledPolyline(int sizeX, int sizeY, String penColor, int penWidth, List<List<Point>> polylines)
	{
		int upsampleFactor = 4;
		BufferedImage image = new BufferedImage(sizeX * upsampleFactor, sizeY * upsampleFactor, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = image.createGraphics();

		// Color trasparentColor = new Color(255, 255, 255);
		// g2d.setColor(trasparentColor);
		// g2d.fillRect(0, 0, sizeX * upsampleFactor, sizeY * upsampleFactor);
		Color penColorArray = Color.decode(penColor);
		g2d.setColor(penColorArray);
		int p = upsampleFactor * penWidth / 2;
		int minX = 0;
		int maxX = sizeX;
		int minY = 0;
		int maxY = sizeY;
		if (polylines.size() > 0)
		{
			Point point = polylines.get(0).get(0);
			int x = upsampleFactor * point.x;
			int y = upsampleFactor * point.y;
			g2d.fillOval(x - p, y - p, p * 2, p * 2);

			for (List<Point> pointList : polylines)
			{
				for (int i = 0; i < pointList.size() - 2; i++)
				{
					Point point1 = pointList.get(i);
					int x1 = upsampleFactor * point1.x;
					int y1 = upsampleFactor * point1.y;
					int c = i + 1;
					Point point2 = pointList.get(c);
					int x2 = upsampleFactor * point2.x;
					int y2 = upsampleFactor * point2.y;

					int dx = x2 - x1;
					int dy = y2 - y1;
					double d = new Double(Math.hypot(dx, dy)).intValue();
					if (d > 0)
					{

						double s = p / d;
						int[] xPoints = new int[]
						{ new Double(x1 - s * dy).intValue(), new Double(x2 - s * dy).intValue(), new Double(x2 + s * dy).intValue(),
								new Double(x1 + s * dy).intValue() };
						int[] yPoints = new int[]
						{ new Double(y1 + s * dx).intValue(), new Double(y2 + s * dx).intValue(), new Double(y2 - s * dx).intValue(),
								new Double(y1 - s * dx).intValue() };

						Polygon polygon = new Polygon(xPoints, yPoints, 4);
						g2d.fillPolygon(polygon);
						g2d.fillOval(x2 - p, y2 - p, p * 2, p * 2);
					}
				}
			}
			g2d.dispose();
		}
		int type = image.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : image.getType();
		BufferedImage resizeImagePng = null;

		resizeImagePng = resizeImageWithHint(image, type, sizeX, sizeY);

		return resizeImagePng;

	}

	private static BufferedImage resizeImageWithHint(BufferedImage originalImage, int type, int width, int height)
	{
		return resizeImageWithHint(originalImage, type, 0, 0, width, height);
	}

	private static BufferedImage resizeImageWithHint(BufferedImage originalImage, int type, int x, int y, int width, int height)
	{

		BufferedImage resizedImage = new BufferedImage(width, height, type);
		Graphics2D g = resizedImage.createGraphics();
		g.drawImage(originalImage, x, y, width, height, null);
		g.dispose();
		g.setComposite(AlphaComposite.Src);

		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		return resizedImage;
	}
}
